/**
 * \class Server
 *
 * \note Debugging on the Server class was done by TW Foster
 *
 * \author TW Foster
 *
 * \version $Revision: 2.3 $
 *
 * \date $Date: 2014/09/24 21:25:06 $
 *
 * Created on: Thurs Sep 24 14:15:56 2014
 *
 */

#include <QtCore>
#include "Server.h"
#include "ClientHandle.h"
#include <QNetworkInterface>
#include "gamemaster.h"

Server::Server(QObject *parent) : QTcpServer(parent)
{
    num_clients = 0;

	// Used to determine which type of message was received.
	// Each time the server requests something from the client, the functionFlag is set.
    functionFlag = "";

	// Used to determine whether the client is sending messages in plain text or cipher.
	// If messages are encrypted, the reply to such a message is also encrypted.
	// If the message is received in plain text, the response is sent as plain text also.
    plaintext = false;

	// Start listening for clients.
    if (!this->listen(QHostAddress::Any,5555)) {
        qDebug() << (tr("Unable to start the server: %1.").arg(this->errorString()));
        close();
        return;
    } else {
        qDebug() << (tr("Server listening on port 5555."));
    }

    qDebug() << getIP();

    gm = new GameMaster(this);

    crypto = new SimpleCrypt(0x6272657474667477);

    // Discover registration server
    udpsocket = new QUdpSocket(this);
    udpsocket->bind(9051);
    connect(udpsocket, SIGNAL(readyRead()), this, SLOT(dataReceived()));

	// Create a discover JSON object and broadcast it to all available addresses.
    QJsonObject obj;
    obj["type"] = "discover";
    udpsocket->writeDatagram( QJsonDocument(obj).toJson(), QHostAddress::Broadcast,9050);

    regServerSocket = new QTcpSocket(this);
    connect(regServerSocket, SIGNAL(connected()), this, SLOT(regServerConnected()));
    connect(regServerSocket, SIGNAL(readyRead()), this, SLOT(regServerRead()));
}

void Server::dataReceived()
{
	// Check to see when the registration server has replied to the broadcast.
	// When it has, save it's IP address and connect using socket->connectToHost.
    if( udpsocket->hasPendingDatagrams())
    {
        qint64 size = udpsocket->pendingDatagramSize();
        char *data = (char*)calloc(size, sizeof(char));

        QHostAddress temp_add;
        qint64 r = udpsocket->readDatagram( data, size, &temp_add);

        if( r>0 && temp_add.toString() == "137.215.155.20")
        {
            //QByteArray message(data);
            regServerAddress = temp_add;
            regServerSocket->connectToHost(regServerAddress,9077);
        }
    }
}

void Server::regServerConnected()
{
	// Let the registration server know that our server is listening
	// on the relevant port and IP address.
    QJsonObject json;
    json.insert("type", QJsonValue("register"));
    json.insert("name", QJsonValue("Group F Server"));
    json.insert("ip", QJsonValue(getIP()));
    json.insert("port", QJsonValue("5555"));

    QJsonDocument doc(json);
    QByteArray bytes = doc.toJson();
    //QString req = QString::fromUtf8(doc.toJson());
    //std::string utf = req.toUtf8().constData();

	// Encrypt the message and send it.
    bytes = crypto->encryptToByteArray(bytes);

    regServerSocket->write(bytes);
    regServerSocket->waitForBytesWritten(2000);
}

void Server::regServerRead()
{
	// Read messages from the registration server, for debugging.
    QString str(crypto->decryptToByteArray(regServerSocket->readAll()));
    qDebug() << str;
}

QString Server::getIP()
{
	// Get the server's real IP address.
    QString temp = "";
    int i = 0;
    foreach (const QHostAddress &address, QNetworkInterface::allAddresses()) {
        if (address.protocol() == QAbstractSocket::IPv4Protocol && address != QHostAddress(QHostAddress::LocalHost))
        {
            i++;
            if(i == 1)
                temp = address.toString();
        }
    }
    return temp;
}

void Server::incomingConnection(qintptr socketDescriptor)
{
	// When a client joins, create a new thread and connect it to various sockets.
    qDebug() << endl << QString("Incoming client connection detected.");

    ClientThread *client = new ClientThread(socketDescriptor);
    connect(client, SIGNAL(receivedMessage(QByteArray)), this, SLOT(processMsg(QByteArray)));
    connect(client, SIGNAL(clientClosed(int)), this, SLOT(threadFinished(int)), Qt::DirectConnection);
    connect(client, SIGNAL(finished()), client, SLOT(deleteLater()));

    clientList.append(client);

    client->start();
}

void Server::processMsg(const QByteArray &msg)
{
	// Parse the message received from the client.
    QDebug debug = qDebug();

    ClientThread *clSender = (ClientThread*)sender();
    ClientHandle *client = clSender->getClient();
    plaintext = false;

	// If the client's name isn't null, then they have already joined.
    if( !client->getName().isNull())
        debug << QString(client->getName());
    else
        debug << QString("New client has joined.");

    QString str(crypto->decryptToByteArray(msg));

    qDebug() << "ORIGINAL STRING" << str;

    // If the message is not encrypted, just chill out. Send the next message unencrypted.
    if( str == "")
    {
        str = QString(msg);
        qDebug() << "NOT A CYPHER" << str;
        plaintext = true;
    }

    //debug << str;

    //QString str = msg;
    str = str.replace("{", "|");
    str = str.replace("}", "|");
    //qDebug() << str;

    QStringList split = str.split("|");
    QStringList reals;

    for (int i = 0; i < split.size(); ++i)
        if( split.at(i).length() > 4)
            reals.append(QString(tr("{%1}").arg(split.at(i))));

    QList<QJsonObject> json_list;
    for( int i=0; i<reals.size(); ++i)
    {
        QJsonDocument jdoc = QJsonDocument::fromJson(reals.at(i).toUtf8());
        QJsonObject jobj = jdoc.object();
        json_list.append(jobj);
    }

    for( int i=0; i<json_list.size(); ++i)
    {
        QString type;
        QJsonObject obj = json_list.at(i);

        type = obj["type"].toString().toUpper();
        //qDebug() << QString(tr("Message received: %1").arg(type));

        if( type == "JOIN")
        {
            if( num_clients++ < 4)
            {
                //clientList.at(num_clients)->getClient()->setName(obj["screen_name"].toString());
                client->setName(obj["screen_name"].toString());

                //clientNames.append(obj["screen_name"].toString());

                QJsonObject json;
                json.insert("type", QJsonValue("player_list"));

                if( clientList.size() == 1) {
                    json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                    json.insert("south", QJsonValue(""));
                    json.insert("east", QJsonValue(""));
                    json.insert("west", QJsonValue(""));
                } else if( clientList.size() == 2) {
                    json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                    json.insert("south", QJsonValue(clientList.at(1)->getClient()->getName()));
                    json.insert("east", QJsonValue(""));
                    json.insert("west", QJsonValue(""));
                } else if( clientList.size() == 3) {
                    json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                    json.insert("south", QJsonValue(clientList.at(1)->getClient()->getName()));
                    json.insert("east", QJsonValue(clientList.at(2)->getClient()->getName()));
                    json.insert("west", QJsonValue(""));
                } else if( clientList.size() == 4) {
                    json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                    json.insert("south", QJsonValue(clientList.at(1)->getClient()->getName()));
                    json.insert("east", QJsonValue(clientList.at(2)->getClient()->getName()));
                    json.insert("west", QJsonValue(clientList.at(3)->getClient()->getName()));
                }

				// If four clients have joined, the game is ready.
				// Thus we send out the full player list to everyone and start the gamemaster.
                if( num_clients == 4)
                {
                    functionFlag = "PLAYER LIST";
                    multicastMsg(json);

                    for( int i=0; i<4; i++) {
                        string s = clientList.at(i)->getClient()->getName().toUtf8().constData();
                        gm->setPlayerName(i,s);
                    }

                    gm->start();
                }
				// If there arn't enough clients, just let everyone know what's happening.
                else
                {
                    functionFlag = "PLAYER LIST";
                    multicastMsg(json);
                }
            }
            else
            {
                //sendAcknowledge( client, 0, 8);
            }
        }
        else if( type == "NEW_SCREEN_NAME")
        {
            ClientHandle *c = getClientByName( obj["Screen Name"].toString());
            c->setName("New Screen Name");
            //sendAcknowledge( client, 1, 0);
        }
        else if( type == "REQUEST_SLOT_CHANGE")
        {

        }
        else if( type == "BID")
        {
            QJsonObject obj = json_list.at(i);
            QStringList list;
            list.append(obj["bid_action"].toString().toUpper());
            list.append(obj["trick_count"].toString().toUpper());
            list.append(obj["trump_suit"].toString().toUpper());

            //gm->sendBid(list);
            client_requested = list;

            QString name = obj["screen_name"].toString();

            sendAcknowledge(0, name, 1, 0);
        }
		// This function is vitally important.
		// It determines which acknowledgement was received and how to act based on this information.
        else if( type == "ACKNOWLEDGEMENT")
        {
            if( functionFlag == "PLAYER LIST")
            {
                debug << "Player list ack.";
            }
            else if( functionFlag == "PHASE")
            {
                debug << "Phase ack.";
                ack_phase = true;
            }
            else if( functionFlag.toUpper() == "SEND HAND OWN")
            {
                debug << "Send own hand ack.";
                ack_sendcards = true;
            }
            else if( functionFlag.toUpper() == "SEND HAND DUMMY")
            {
                debug << "Send dummy hand ack.";
                ack_sendcards = true;
            }
            else if( functionFlag.toUpper() == "DEALING")
            {
                debug << "Dealing ack.";
                ack_phase = true;
            }
            else if( functionFlag.toUpper() == "REDEALING")
            {
                debug << "Redealing ack.";
                ack_phase = true;
            }
            else if( functionFlag.toUpper() == "BIDDING")
            {
                debug << "Bidding ack.";
                ack_phase = true;
            }
            else if( functionFlag.toUpper() == "PLAY")
            {
                debug << "Play ack.";
                ack_phase = true;
            }
            else if( functionFlag == "REQUEST BID")
            {
                debug << "Request bid ack.";
                //gm->ack_sendBid();
            }
            else if( functionFlag == "CONTRACT")
            {
                debug << "Contract ack.";
                ack_contract = true;
            }
            else if( functionFlag == "DUMMY ID")
            {
                debug << "Dummy ID ack.";
                ack_dummyid = true;
            }
            else if( functionFlag == "BID UPDATE")
            {
                debug << "Bid Update ack.";
                ack_bidupdate = true;
            }
            else if( functionFlag == "SEND GAME STATUS")
            {
                debug << "Game Status ack.";
                ack_sendstatus = true;
            }
            else if( functionFlag == "SEND MOVE")
            {
                debug << "Send move ack.";
                ack_sendmove = true;
            }
            else if( functionFlag == "SEND WON UPDATE")
            {
                debug << "Send won update ack.";
                ack_wonupdate = true;
            }
            else if( functionFlag == "SCORE UPDATE")
            {
                debug << "Send score update ack";
                ack_scoreupdate = true;
            }
        }
        else if( type == "PLAY_CARD")
        {
            //debug << str;

            QJsonObject obj = json_list.at(i);
            QString name = obj["screen_name"].toString();

            int i;
            for( i=0; i<4; i++)
                if( clientList.at(i)->getClient()->getName() == name)
                    break;

            QStringList list;
            list.append(obj["suit"].toString().toUpper());
            list.append(obj["value"].toString().toUpper());
            list.append(QString::number(i));

            client_requested = list;

            sendAcknowledge(0, name, 1, 0);
        }
        else if( type == "CLIENT_QUITS")
        {
            debug << "Client sent quit message";
            QJsonObject obj = json_list.at(i);
            QString name = obj["screen_name"].toString();
            gm->playerQuit( name);

            for( int i=0;i<clientList.size(); i++)
                if( clientList.at(i)->getClient()->getName() == name)
                {
                    clientList.removeAt(i);
                    break;
                }

            QJsonObject json;
            json.insert("type", QJsonValue("player_list"));

            if( clientList.size() == 1) {
                json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                json.insert("south", QJsonValue(""));
                json.insert("east", QJsonValue(""));
                json.insert("west", QJsonValue(""));
            } else if( clientList.size() == 2) {
                json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                json.insert("south", QJsonValue(clientList.at(1)->getClient()->getName()));
                json.insert("east", QJsonValue(""));
                json.insert("west", QJsonValue(""));
            } else if( clientList.size() == 3) {
                json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                json.insert("south", QJsonValue(clientList.at(1)->getClient()->getName()));
                json.insert("east", QJsonValue(clientList.at(2)->getClient()->getName()));
                json.insert("west", QJsonValue(""));
            } else if( clientList.size() == 4) {
                json.insert("north", QJsonValue(clientList.at(0)->getClient()->getName()));
                json.insert("south", QJsonValue(clientList.at(1)->getClient()->getName()));
                json.insert("east", QJsonValue(clientList.at(2)->getClient()->getName()));
                json.insert("west", QJsonValue(clientList.at(3)->getClient()->getName()));
            }

            functionFlag = "PLAYER LIST";
            multicastMsg(json);
        }
        else if( type == "CLIENT_ERROR")
        {

        }
        else if( type == "BID_UPDATE")
        {

        }
        else
        {

        }
    }
}

void Server::threadFinished(int threadId)
{
    QString userExited;
    for (int i = 0; i < clientList.size(); i++)
    {
        if (clientList.at(i)->getThreadId() == threadId)
        {
            userExited = clientList.at(i)->getClient()->getName();
            clientList.removeAt(i);
        }
    }
}

void Server::multicastMsg(QJsonObject json)
{
	// Send a message to everyone connected to the server.
    QJsonDocument doc(json);
    //QString req = QString::fromUtf8(doc.toJson());

    QByteArray bytes = doc.toJson();
    bytes = crypto->encryptToByteArray(bytes);

    qDebug() <<  clientList.size();

    for (int i = 0; i < clientList.size(); i++)
        clientList.at(i)->sendMessage(bytes);//req.toLatin1());
}

QList<ClientHandle*> Server::getClients() const
{
	// Get a QList pointer to all the clients
    QList<ClientHandle*> clients;
    for (int i = 0; i < clientList.size(); i++)
        clients.append(clientList.at(i)->getClient());
    return clients;
}

ClientHandle * Server::getClient(int _id) const
{
	// Get a client by socket descriptor.
    for (int i = 0; i < clientList.size(); i++)
        if (clientList.at(i) and clientList.at(i)->getClient()->getId() == _id)
            return clientList.at(i)->getClient();
    return NULL;
}

ClientHandle * Server::getClientByName(const QString &_name) const
{
	// Get a client by name.
    for (int i = 0; i < clientList.size(); i++)
        if (clientList.at(i) and clientList.at(i)->getClient()->getName() == _name)
            return clientList.at(i)->getClient();
    return NULL;
}

ClientThread * Server::getClientThreadByName(const QString &_name) const
{
	// Get a client thread by client name.
    for (int i = 0; i < clientList.size(); i++)
        if (clientList.at(i) and clientList.at(i)->getClient()->getName() == _name)
            return clientList.at(i);
    return NULL;
}

void Server::writeToClients(const QString &_msg)
{
	// Writes the same message unencrypted to all clients.
    for (int i = 0; i < clientList.size(); i++)
        clientList.at(i)->sendMessage(_msg.toLatin1());
}

void Server::writeJSON(QJsonObject json, ClientThread *client)
{
	// Writes a JSON object to the network. Whether it's encrypted or not
	// depends on whether or note plaintext is true.
    QJsonDocument doc(json);

    QByteArray bytes = doc.toJson();

    if( !plaintext)
        bytes = crypto->encryptToByteArray(bytes);

    //QString req = QString::fromUtf8(doc.toJson());
    client->sendMessage(bytes);//req.toLatin1());
}

QStringList Server::getBid( int, QString user_name)
{
    if( functionFlag != "REQUEST BID")
    {
        ClientThread *client = getClientThreadByName(user_name);

        QJsonObject json;
        json.insert("type", QJsonValue("request"));
        json.insert("note", QJsonValue("bid"));

        functionFlag = "REQUEST BID";

        client_requested.clear();

        writeJSON(json, client);
    }
    else
    {
        if( !client_requested.isEmpty())
        {
            functionFlag = "";
        }
    }

    return client_requested;
}

bool Server::sendBid( int, QString client_to, QString client_name, QString bid_action, QString trick_count, QString trump_suit)
{
    if( functionFlag != "BID UPDATE")
    {
        //qDebug() << "client to" << client_to;
        //qDebug() << "client_name" << client_name;

        ClientThread *client = getClientThreadByName(client_to);

        QJsonObject json;
        json.insert("type", QJsonValue("bid_update"));
        json.insert("screen_name", QJsonValue(client_name));
        json.insert("bid_action", QJsonValue(bid_action));
        json.insert("trick_count", QJsonValue(trick_count));
        json.insert("trump_suit", QJsonValue(trump_suit));

        functionFlag = "BID UPDATE";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_bidupdate)
        {
            return false;
        }
        else
        {
            ack_bidupdate = false;
            functionFlag = "";
        }
    }

    return true;
}

QStringList Server::getMove( int, QString name)
{
    if( functionFlag != "GET MOVE")
    {
        ClientThread *client = getClientThreadByName(name);

        QJsonObject json;
        json.insert("type", QJsonValue("request"));
        json.insert("note", QJsonValue("play"));

        functionFlag = "GET MOVE";

        client_requested.clear();

        writeJSON(json, client);
    }
    else
    {
        if( !client_requested.isEmpty())
        {
            functionFlag = "";
        }
    }

    return client_requested;
}

QStringList Server::getDummyMove( int, QString name)
{
    if( functionFlag != "GET DUMMY MOVE")
    {
        ClientThread *client = getClientThreadByName(name);

        QJsonObject json;
        json.insert("type", QJsonValue("request"));
        json.insert("note", QJsonValue("dummy_play"));

        functionFlag = "GET DUMMY MOVE";

        client_requested.clear();

        writeJSON(json, client);
    }
    else
    {
        if( !client_requested.isEmpty())
        {
            functionFlag = "";
        }
    }

    return client_requested;
}

bool Server::sendMove( QString client_name, int id, QString suit, QString value)
{
    if( functionFlag != "SEND MOVE")
    {
        ClientThread *client = getClientThreadByName(client_name);

        QJsonObject json;
        json.insert("type", QJsonValue("play_update"));
        json.insert("screen_name", QJsonValue(clientList.at(id)->getClient()->getName()));
        json.insert("suit", QJsonValue(suit));
        json.insert("value", QJsonValue(value));

        functionFlag = "SEND MOVE";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_sendmove)
        {
            return false;
        }
        else
        {
            ack_sendmove = false;
            functionFlag = "";
        }
    }

    return true;
}

void Server::sendAcknowledge(int, QString name, int success, int error_code)
{
    ClientThread *client = getClientThreadByName(name);

    QJsonObject json;
    json.insert("type", QJsonValue("acknowledgement"));
    json.insert("note", QJsonValue(success));
    json.insert("error_code", QJsonValue(error_code));

    writeJSON(json, client);
}

bool Server::sendPhase(int, QString user_name, QString phase)
{
    if( functionFlag != phase)
    {
        ClientThread *client = getClientThreadByName(user_name);

        QJsonObject json;
        json.insert("type", QJsonValue("phase"));
        json.insert("note", QJsonValue(phase));

        functionFlag = phase;

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_phase)
        {
            return false;
        }
        else
        {
            ack_phase = false;
            functionFlag = "";
        }
    }

    return true;
}

bool Server::sendContract(QString client_name, int trick_count, QString trump_suit)
{
    if( functionFlag != "CONTRACT")
    {
        ClientThread *client = getClientThreadByName(client_name);

        QJsonObject json;
        json.insert("type", QJsonValue("contract"));
        json.insert("trick_count", QJsonValue(QString::number(trick_count)));
        json.insert("trump_suit", QJsonValue(trump_suit));

        functionFlag = "CONTRACT";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_contract)
        {
            return false;
        }
        else
        {
            ack_contract = false;
            functionFlag = "";
        }
    }

    return true;
}

bool Server::sendWonUpdate( QString client_to, QString team, QString player, QString note)
{
    if( functionFlag != "SEND WON UPDATE")
    {
        ClientThread *client = getClientThreadByName(client_to);

        QJsonObject json;
        json.insert("type", QJsonValue("won_update"));
        json.insert("winning_team", QJsonValue(team));
        json.insert("note", QJsonValue(note));

        functionFlag = "SEND WON UPDATE";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_wonupdate)
        {
            return false;
        }
        else
        {
            ack_wonupdate = false;
            functionFlag = "";
        }
    }

    return true;
}

bool Server::sendDummyID( int, QString client_name, QString dummy_name)
{
    if( functionFlag != "DUMMY ID")
    {
        ClientThread *client = getClientThreadByName(client_name);

        QJsonObject json;
        json.insert("type", QJsonValue("dummy_identification"));
        json.insert("dummy", QJsonValue(dummy_name));

        functionFlag = "DUMMY ID";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_dummyid)
        {
            return false;
        }
        else
        {
            ack_dummyid = false;
            functionFlag = "";
        }
    }

    return true;
}

bool Server::sendCardArray( int, QString user_name, QString cards, QString note)
{
    if( functionFlag != ("SEND HAND " + note))
    {
        ClientThread *client = getClientThreadByName(user_name);

        QJsonObject json;
        json.insert("type", QJsonValue("dealt_cards"));
        json.insert("card_array", QJsonValue(cards));
        json.insert("note", QJsonValue(note));

        functionFlag = "SEND HAND " + note;

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_sendcards)
        {
            return false;
        }
        else
        {
            ack_sendcards = false;
            functionFlag = "";
        }
    }

    return true;
}

bool Server::scoreUpdate( QString user_name, QString wabove, QString wbelow, QString tabove, QString tbelow)
{
    if( functionFlag != "SCORE UPDATE")
    {
        ClientThread *client = getClientThreadByName(user_name);

        QJsonObject json;
        json.insert("type", QJsonValue("score_update"));
        json.insert("we_above", QJsonValue(wabove));
        json.insert("we_below", QJsonValue(wbelow));
        json.insert("they_above", QJsonValue(tabove));
        json.insert("they_below", QJsonValue(tbelow));

        functionFlag = "SCORE UPDATE";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_scoreupdate)
        {
            return false;
        }
        else
        {
            ack_scoreupdate = false;
            functionFlag = "";
        }
    }

    return true;
}

bool Server::sendGameStatus( QString user_name, QStringList list)
{
    if( functionFlag != ("SEND GAME STATUS"))
    {
        ClientThread *client = getClientThreadByName(user_name);

        QJsonObject json;
        json.insert("type", QJsonValue("status"));
        json.insert("trick_number", QJsonValue(list.at(0)));
        json.insert("deal_number", QJsonValue(list.at(1)));
        json.insert("game_number", QJsonValue(list.at(2)));

        functionFlag = "SEND GAME STATUS";

        writeJSON(json, client);

        return false;
    }
    else
    {
        if(!ack_sendstatus)
        {
            return false;
        }
        else
        {
            ack_sendstatus = false;
            functionFlag = "";
        }
    }

    return true;
}
